<?php
	/**
	 * User: sfy
	 * Date: 2016/7/27
	 * Time: 8:58
	 */

include_once dirname(__FILE__).'/np_db_mysql.class.php';
include_once dirname(__FILE__).'/np_db_mysqli.class.php';
include_once dirname(__FILE__).'/np_db_mysql_with_cache.class.php';
include_once dirname(__FILE__).'/np_db.class.php';

	class np_db_mysql_with_hash_table extends np_db_class
	{

		private $db_inst = null;

		private $opened = false;

		private $str_sqls = array();

		private static $g_db_hash_table_rules = null;
		private static $g_db_hash_table_modes = null;
/*
 *    0为正常模式，只读写新表
 *    1为初始化模式，即旧表将数据导入到新的分表中的过度模式，新旧表双写，只读旧表
 *    2为兼容模式，即线上运行时为了验证可靠性，保证业务可以回滚的过度模式，新旧表双写，只读新表
		*/
		private $execute_mode = 0;


		public function __construct ($config)
		{

			$this->m_db_config = $config;
			//$extend_class 代表用哪种基础DB对象做底层查询
			$extend_class = $this->m_db_config["db_base_extend_class"];
			switch($extend_class)
			{
				case 'mysql':
					$this->db_inst = new np_db_mysql_class($config);
				break;
				case 'mysqli':
					$this->db_inst = new np_db_mysqli_class($config);
				break;
				case 'mysql_with_cache':
					$this->db_inst = new np_db_mysql_with_cache($config);
				break;
			}

			// var_dump($this->m_db_config["db_hash_table_rules"]);

			if (is_array($this->m_db_config["db_hash_table_rules"]))
			{
				// 获取配置中的HASH算法，并转换成数组对象
				$this->__check_db_rules
				(
					$this->m_db_config["db_hash_table_rules"]
					// $this->m_db_config["db_name"]
				);
			}

			if (is_array($this->m_db_config["db_hash_table_modes"]))
			{
				// 获取配置中的HASH算法，并转换成数组对象
				$this->__check_db_mode
				(
					$this->m_db_config["db_hash_table_modes"]
				);
			}

			// var_dump($this->db_hash_table_rules);
		}

		public function set_db_inst($db_inst)
		{
			if (is_array($db_inst))
			{
				$this->db_inst = $db_inst;
			}
		}

		private function __check_db_mode($db_mode)
		{
			if (is_array(self::$g_db_hash_table_modes))
			{
				return;
			}
			$db_hash_table_modes = array();
			foreach ($db_mode as $item)
			{
				$item = trim($item);
				$rule = explode('.',$item);
				if (count($rule) == 3 && is_numeric($rule[2]) )
				{
					
					if (!$db_hash_table_modes[$rule[0]])
					{
						$db_hash_table_modes[$rule[0]] = array();
					}
					$db_hash_table_modes[$rule[0]][$rule[1]] = $rule[2]; 
					// if ($dbname == $rule[0])
					// {
					// 	$rules[] = $rule;
					// }
				}
			}
			self::$g_db_hash_table_modes = $db_hash_table_modes;

			unset($db_hash_table_modes);
		}

		//根据表名获取对应表的运行模式
		private function __get_mode_by_tablename($table_name)
		{
			$mode = self::$g_db_hash_table_modes[$this->m_db_config["db_name"]];
			if (!is_array($mode))
			{
				return 0;
			}
			
			return $mode[$table_name]?$mode[$table_name]:0;
		}

		/**
		*将格式为array("nn_cms.test.id.100","nn_cms.xxx.order.20")之类的规则转换为数组类规则
		* nn_cms.test.id.100代表nn_cms库的test表按ID字段进行划分，并分配到100张表中。
		**/
		private function __check_db_rules($db_rules)
		{

			if (is_array(self::$g_db_hash_table_rules))
			{
				return ;
			}

			$db_hash_table_rules = array();
			foreach ($db_rules as $item)
			{
				$item = trim($item);
				$rule = explode('.',$item);
				if (count($rule) == 4 && is_numeric($rule[3]) )
				{

					if (!$db_hash_table_rules[$rule[0]])
					{
						$db_hash_table_rules[$rule[0]] = array();
					}
					$db_hash_table_rules[$rule[0]][] = $rule; 
					// if ($dbname == $rule[0])
					// {
					// 	$rules[] = $rule;
					// }
				}
			}
			self::$g_db_hash_table_rules = $db_hash_table_rules;
			unset($db_hash_table_rules);
		}
		//根据表名获取对应表的HASH规则
		private function __get_rule_by_tablename($table_name)
		{
			$rules = self::$g_db_hash_table_rules[$this->m_db_config["db_name"]];
			if (!is_array($rules))
			{
				return null;
			}

			foreach ($rules as $rule)
			{
				if ($rule[1] == $table_name)
				{
					return $rule;
				}
			}
			return null;
		}


		//对insert类的SQL进行HASH解析，将一个SQL拆分为多个SQL
		private function __get_child_sqls_from_rules_by_insert($sql)
		{
			$sqls = array();
			$reg = "/^\s*(insert\s*into|replace\s*into)\s*(.*?)\s*\((.*?)\)\s*values\s*/i";
			preg_match($reg,$sql,$out);
			// var_dump($out);die;
			$sql_type = $out[1];
			$table_name = $out[2];
			$table_insert_fileds = $out[3];
			$table_insert_values = str_replace($out[0], "", $sql);
			// var_dump($table_insert_values);
			// $rule = array("nn_cms","test1","id","2");
			$rule = $this->__get_rule_by_tablename($table_name);
			// echo ($table_insert_values."\n");
			if ($rule === null)
			{
				$sqls[] = $sql; 
				return $sqls;
			}
			$this->execute_mode = $this->__get_mode_by_tablename($table_name);

			$hash_field = $rule[2];
			$hash_slot_num = $rule[3];
			if (!is_numeric($hash_slot_num))
			{
				$sqls[] = $sql; 
				return $sqls;
			}

			$params_values = $this->__get_param_values_from_insert($hash_field,$table_insert_fileds,$table_insert_values,$hash_slot_num);
			if (count($params_values) == 0)
			{
				$sqls[] = $sql; 
				return $sqls;
			}

			foreach ($params_values as $order=>$params_strs)
			{
				$real_table_name = $table_name."__".$order;
				$params_str = implode(",", $params_strs);
				$real_sql = "{$sql_type} {$real_table_name} ({$table_insert_fileds}) values {$params_str};";
				$sqls[] = $real_sql;
			}

			return $sqls;
		}
		//获取insert类SQL对应field的值，并对值进行求模HASH
		private function __get_param_values_from_insert($hash_field,$fields,$values_str,$hash_slot_num)
		{
			$params_values = array();
			$p_fields = explode(",", $fields);
			// var_dump($fields);
			$field_index = -1;
				
			foreach ($p_fields as $key=>$field_value)
			{
				$field_value = trim($field_value,"'\" ");
				if ($field_value == $hash_field)
				{
					$field_index = $key;
					break;
				}
			}

			if ($field_index == -1)
			{
				return $params_values;
			}
			// var_dump($values_str);
			$values_str = trim($values_str,";() ");
			$values_str = preg_replace("/\)\s*,\s*\(/", "|#", $values_str);
			$outs = explode("|#", $values_str);

			if (!is_array($outs) || count($outs) == 0)
			{
				return $params_values;
			}

			foreach ($outs as $key=>$params_str)
			{
				$p_values = explode(",", $params_str);
				$params_value = $p_values[$field_index];
				$params_value = trim($params_value,"'\" ");
				$order = $this->__get_hash_key_order($params_value,$hash_slot_num);
				$params_values[$order][] = "({$params_str})";

			}
			// var_dump($outs);die;
			return $params_values;
		}

		//对select或UPDATE类的SQL进行HASH解析，将一个SQL拆分为多个SQL
		private function __get_child_sqls_from_rules_by_where($sql)
		{

			$sqls = array();
			$reg = "/^\s*(select\s.*?\sfrom|update|delete\s*from)\s*(.*?)\s.*?where\s*(.*?)$/i";
			preg_match($reg,$sql,$out);
			$table_name = $out[2];
			$table_where_str = $out[3];
			// $rule = array("nn_cms","test1","id","100");
			$rule = $this->__get_rule_by_tablename($table_name);
			if ($rule === null)
			{
				$sqls[] = $sql; 
				return $sqls;
			}
			$this->execute_mode = $this->__get_mode_by_tablename($table_name);

			$hash_field = $rule[2];
			$hash_slot_num = $rule[3];
			if (!is_numeric($hash_slot_num))
			{
				$sqls[] = $sql; 
				return $sqls;
			}

			$params_values = $this->__get_param_values_from_where($hash_field,$table_where_str,$hash_slot_num);
			if (count($params_values) == 0)
			{
				$sqls[] = $sql; 
				return $sqls;
			}

			foreach ($params_values as $order=>$params_value)
			{
				$real_table_name = $table_name."__".$order;
				$real_sql = str_replace(" ".$table_name." ", " ".$real_table_name." ", $sql);
				$sqls[] = $real_sql;
			}
			return $sqls;
		}

		
		//根据值以及HASH槽总数进行求模，返回HASH槽位
		private function __get_hash_key_order($value,$slot)
		{
			// if (is_numeric($value))
			// {
			// 	$key = abs($value%$slot);
			// }else{
				$md5 = md5($value);
				$key = substr($md5, -4,4);
				$key = hexdec($key);
				$key = $key%$slot;
			// }
			return $key;
		}

		//获取WHERE类SQL对应field的值，并对值进行求模HASH
		private function __get_param_values_from_where($hash_field,$table_where_str,$hash_slot_num)
		{
			$params_values = array();
			$where_reg = "/\s*".$hash_field."\s*=\s*([^\s\;]*)\s*/i";

			$bool = preg_match($where_reg,$table_where_str,$params_out);
			if ($bool)
			{
				$params_value = $params_out[1];
			
				$params_value = trim($params_value,"'\"");
				if (strlen($params_value)>0)
				{
					$order = $this->__get_hash_key_order($params_value,$hash_slot_num);
					$params_values[$order][] = $params_value;
				}

				return $params_values;
			}
			
			$where_reg = "/\s*".$hash_field."\s*in\s*\(([^\)]*?)\)/i";

			$bool = preg_match($where_reg,$table_where_str,$params_out);
				
			if ($bool)
			{
				$params_str = $params_out[1];
				$p_values = explode(",", $params_str);
				
				foreach ($p_values as $params_value)
				{
					if (strlen($params_value)>0)
					{
						$params_value = trim($params_value,"'\" ");
						$order = $this->__get_hash_key_order($params_value,$hash_slot_num);
						$params_values[$order][] = $params_value;
					}
				}
				return $params_values;
			}
			
			return $params_values;
		}

		//对show类的SQL进行SQL分解，此类语句一般是查询表状态或结构等，所以可以直接转换为单表操作
		private function __get_child_sqls_from_rules_by_single_table($sql)
		{

			$sqls = array();
			$reg = "/^\s*(show\s.*?\sfrom)\s*(.*?)\s/i";
			preg_match($reg,$sql,$out);
			$table_name = $out[2];
			// $rule = array("nn_cms","test1","id","100");
			$rule = $this->__get_rule_by_tablename($table_name);
			if ($rule === null)
			{
				$sqls[] = $sql; 
				return $sqls;
			}

			$real_table_name = $table_name."__0";
			$real_sql = str_replace(" ".$table_name." ", " ".$real_table_name." ", $sql);
			$sqls[] = $real_sql;
			return $sqls;
		}


		public function query ($sql)
		{
			
			$this->execute_mode == 0;
			if (strpos(strtolower($sql),"show") === 0 )
			{
				$this->str_sqls = $this->__get_child_sqls_from_rules_by_single_table($sql);
			}else
			{
				$this->str_sqls = $this->__get_child_sqls_from_rules_by_where($sql);
			}

			//当为初始化模式时，读取旧表
			if ($this->execute_mode == 1)
			{
				$this->str_sqls = array($sql);
				return true;
			}

			return true;
		}

		public function open()
		{
			$this->opened = $this->db_inst->open();
			return $this->opened;
		}

		public function close()
        {
			if ($this->opened === true && !is_null($this->db_inst))
			{
				$this->db_inst->close();
			}
			return TRUE;
		}

		public function execute($sql)
		{
			$this->execute_mode == 0;
			if ($this->opened === false)
			{
				$this->opened = $this->db_inst->open();
			}
			
			if ($this->opened === false)
			{
				return false;
			}

			$sql = trim($sql);
			
			if (strpos(strtolower($sql),"update") === 0 ||
				strpos(strtolower($sql),"delete") === 0 
				)
			{
		
				$this->str_sqls = $this->__get_child_sqls_from_rules_by_where($sql);
			}else
			{
				$this->str_sqls = $this->__get_child_sqls_from_rules_by_insert($sql);
			}
// var_dump($this->str_sqls);
			$bool = true;
			// $this->db_inst->execute("START TRANSACTION");
			//var_dump($this->str_sqls);die;
			
	// var_dump($this->str_sqls);die;
			foreach ($this->str_sqls as $echo_sql)
			{
				$bool = $this->db_inst->execute($echo_sql);
				if ($bool == false)
				{
					break;
				}
			}

			//如果是初始化模式或兼容模式，那么新旧表都对数据进行修改或写入
			if ($this->execute_mode != 0 && $bool)
			{
				$this->db_inst->execute($sql);
			}

			// if ($bool == true)
			// {
			// 	$this->db_inst->execute("COMMIT");
			// }else{
			// 	$this->db_inst->execute("ROLLBACK");
			// }

			return $bool;
		}

		public function get_query_result($is_free_after_get = TRUE)
		{
			
			if ($this->opened === false)
			{
				$this->opened = $this->db_inst->open();
			}
			
			if ($this->opened === false)
			{
				return false;
			}
			$mixed_query_data = array();

			foreach ($this->str_sqls as $sql)
			{
				$query_data = $this->db_inst->query($sql);
				
				$query_data=$this->db_inst->get_query_result($is_free_after_get);
				if (is_array($query_data))
				{
					$mixed_query_data = array_merge($mixed_query_data,$query_data);
				}
			}

			return $mixed_query_data;
		}
		public function __destruct ()
		{
			$this->close();
			$this->db_inst = null;
		}

		/*
	读取最近一次错误ID
	*/
	 public function last_error_no(){
	 	return $this->db_inst->last_error_no();
	 }
	/*
	读取最近一次错误描述
	*/
	public function last_error_desc(){
		return $this->db_inst->last_error_desc();
	}
	/*
	读取当前DB所有SQL信息
	*/
	public function get_db_debug(){
		return $this->db_inst->get_db_debug();
	}
	/*
	读取当前DB所有CONFIG信息
	*/
	public function get_db_config(){
		return $this->db_inst->get_db_config();
	}
	}